import pymysql
from pymysql.cursors import DictCursor
import json
from flask import Flask, request, jsonify, render_template, Blueprint, redirect, session, current_app, g
import numpy as np
import random
from ..utils import db
import time
import vrplib as vrp
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
from werkzeug.utils import secure_filename
import os
import re
from datetime import datetime
import pytz
from functools import partial

POOL = db.POOL
global_optimized_coordinates = []
GLOBAL_DEMAND = None
GLOBAL_WEIGHTS = None
GLOBAL_CUSTOM_LIST = None
GLOBAL_COORD = None
GLOBAL_POPULATION_SIZE = None
GLOBAL_N_GENERATIONS = None
GLOBAL_MAX_CAPACITY = None
GLOBAL_MUTATION_RATE = None

oe = Blueprint(name="optimize", import_name=__name__, url_prefix='/')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False  # 解决负号“-”显示为方块的问题
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')


# vrp.download_instance('A-n32-k5', '../data')
# vrp.download_solution("F-n45-k4", '../data')


#
# instance = vrp.read_instance(r'E:\PycharmProjects\Graduation_Design\Graduation_Design\data\A-n32-k5.vrp')
# demand = instance['demand']
# weights = instance['edge_weight']
# custom_list = np.arange(1, len(demand))
# coord = instance['node_coord'].T


def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS']


def preprocess_json_string(json_string):
    # 使用正则表达式查找不规范的逗号并去除
    # 查找对象、数组结尾处的逗号
    corrected_string = re.sub(r',\s*([}\]])', r'\1', json_string)
    return corrected_string


def get_now():
    timezone = pytz.timezone("Asia/Shanghai")  # 你可以选择合适的时区
    return datetime.now(timezone).strftime("%Y%m%d%H%M%S")


@oe.route('/get_optimized', methods=['GET'])
def get_optimized():
    global global_optimized_coordinates
    return jsonify(global_optimized_coordinates)


@oe.route('/notifications', methods=['GET'])
def get_notifications():
    # 这里可以是数据库查询逻辑，获取最新的通知
    notifications = query_database_for_notifications()
    return jsonify(notifications)


def reset_notifications_table():
    conn = POOL.connection()
    try:
        with conn.cursor() as cursor:
            # 清空表并重置自增ID
            cursor.execute("TRUNCATE TABLE notifications")
        conn.commit()
    finally:
        conn.close()


def query_database_for_notifications():
    conn = POOL.connection()
    try:
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        # 执行查询
        cursor.execute("SELECT * FROM notifications ORDER BY created_at DESC LIMIT 10")
        # 获取所有结果
        notifications = cursor.fetchall()  # 正确调用 fetchall() 方法
        notifications_list = []
        # 将结果转换为字典列表
        for notification in notifications:
            notification['created_at'] = notification['created_at'].strftime('%Y-%m-%d %H:%M:%S')
            notifications_list.append(notification)
        return notifications_list
    finally:
        cursor.close()
        conn.close()


def insert_notification(message):
    conn = POOL.connection()
    try:
        with conn.cursor() as cursor:
            sql = "INSERT INTO notifications (message) VALUES (%s)"
            cursor.execute(sql, (message,))
        conn.commit()
    finally:
        conn.close()


# 绘制随机解的成本分布图
def plot_random_routes(routes, coord, total_cost, demand):
    fig, ax = plt.subplots(figsize=(6, 6))
    # Depot点
    ax.scatter(coord[0][0], coord[1][0], s=100, c='red')  # 红色代表仓库
    # 客户点
    ax.scatter(coord[0][1:], coord[1][1:], s=demand[1:], c='blue')  # 蓝色代表客户

    # 绘制路线
    for i, route in enumerate(routes):
        # 创建一个随机颜色
        color = np.random.rand(3, )
        plot_list = [0] + route + [0]  # 从仓库出发，到仓库结束
        ax.plot(coord[0][plot_list], coord[1][plot_list], linewidth=1, color=color)

        # 路径上的点
        ax.scatter(coord[0][plot_list], coord[1][plot_list], s=demand[plot_list], c=color,
                   label=f'路线{i + 1}:{sum(demand[route])}')

    # 设置标题和图例
    ax.set_title(f'路线数量:{len(routes)}, 总成本:{int(total_cost)}')
    ax.legend(loc=1, fontsize='x-small')
    plt.show()
    directory = os.path.join(current_app.root_path, 'static', 'random_image')

    if not os.path.exists(directory):
        os.makedirs(directory)

    filename = os.path.join(directory, f'{timestamp}_random_solution.png')

    # 尝试保存图像文件
    try:
        fig.savefig(filename)
        plt.close(fig)
        return filename
    except Exception as e:
        plt.close(fig)
        print(f"Failed to save the image: {e}")
        return None


# 绘制优化解的成本分布图
def plot_routes(best_routes, coord, best_cost, demand):
    fig, ax = plt.subplots(figsize=(6, 6))
    ax.scatter(coord[0][0], coord[1][0], s=100, c='red')  # 红色代表仓库
    ax.scatter(coord[0][1:], coord[1][1:], s=demand[1:], c='blue')  # 蓝色代表客户

    for i, route in enumerate(best_routes):
        color = np.random.rand(3, )
        plot_list = [0] + route + [0]
        ax.plot(coord[0][plot_list], coord[1][plot_list], linewidth=1, color=color)
        ax.scatter(coord[0][plot_list], coord[1][plot_list], s=demand[plot_list], c=color,
                   label=f'路线 {i + 1}: {sum(demand[route])}')

    ax.set_title(f'路线数量:{len(best_routes)}, 总成本:{int(best_cost)}')
    ax.legend(loc=1, fontsize='x-small')
    plt.show()

    # 定义目录
    directory = os.path.join(current_app.root_path, 'static', 'optimization_image')
    if not os.path.exists(directory):
        os.makedirs(directory)
    filename = os.path.join(directory, f'{timestamp}_optimization_solution.png')

    # 尝试保存图像文件
    try:
        fig.savefig(filename)
        plt.close(fig)
        return filename
    except Exception as e:
        plt.close(fig)
        print(f"Failed to save the image: {e}")
        return None


def plot_optimization_checkpoint(checkpoint_costs):
    # 分离数据点为迭代次数和成本
    generations, costs = zip(*checkpoint_costs)

    plt.figure(figsize=(10, 5))
    plt.plot(generations, costs, marker='o', linestyle='-', color='b')
    plt.xlabel('迭代代数')
    plt.ylabel('成本')
    plt.title('遗传算法优化过程')
    plt.xticks(generations)  # 显示所有检查点的迭代次数
    plt.grid(True)
    directory = os.path.join(current_app.root_path, 'static', 'checkpoint_img')
    file_name = f'{timestamp}_optimization_process_checkpoint.png'
    if not os.path.exists(directory):
        os.makedirs(directory)
    optimization_file_path = os.path.join(directory, file_name)

    plt.savefig(optimization_file_path)
    # 保存图像文件
    plt.show()
    return optimization_file_path


def solution_routes(x):
    '''
    解析最佳解
    使用solution_routes(x)函数将最佳解（客户访问顺序）转换为一组路线，每条路线的总需求量不超过车辆的最大容量。
    :param x:
    :return:
    '''
    routes, route, route_sum = [], [], 0
    for i in x:
        if route_sum + GLOBAL_DEMAND[i] >= GLOBAL_MAX_CAPACITY:
            routes.append(route)
            route, route_sum = [], 0
        route.append(i)
        route_sum += GLOBAL_DEMAND[i]
    if (len(route) > 0):
        routes.append(route)

    return routes


def routes_cost(routes):
    '''
    通过routes_cost(routes)函数计算得到的路线集合的总成本，即所有路线的距离之和。
    :param routes:
    :return:
    '''
    cost = 0
    for r in routes:
        rlist = [0] + r + [0]
        for i in range(len(rlist) - 1):
            cost += GLOBAL_WEIGHTS[rlist[i]][rlist[i + 1]]
    return cost


def fitness_function(x):
    routes = solution_routes(x)
    cost = routes_cost(routes)
    return 1 / cost


def selection(population, fitness_values):
    next_population = []
    for _ in range(GLOBAL_POPULATION_SIZE):
        parent = random.choices(population, weights=fitness_values)[0]
        next_population.append(parent)

    return next_population


def mutation(population):
    mut_population = [x.copy() for x in population]
    for i in range(len(mut_population)):
        k = \
            random.choices(np.arange(2, len(GLOBAL_DEMAND) - 1),
                           weights=(1 / (np.arange(2, len(GLOBAL_DEMAND) - 1))) ** GLOBAL_MAX_CAPACITY)[
                0]
        # k代表变异长度
        # print(k)
        idxs = list(set([random.randint(0, len(GLOBAL_DEMAND) - 2) for _ in range(k)]))
        mut_population[i][idxs] = np.random.permutation(mut_population[i][idxs])
    all_population = population + mut_population
    fitness_values = [fitness_function(x) for x in all_population]
    p, topk = fitness_values, GLOBAL_POPULATION_SIZE
    index_list = sorted(range(len(p)), key=lambda i: p[i], reverse=True)
    next_population = []
    for i in range(topk):
        if i < len(index_list):
            next_population.append(all_population[index_list[i]])
        else:
            break

    return next_population


def genetic_algorithm(checkpoint=10):
    population = [np.random.permutation(GLOBAL_CUSTOM_LIST).copy() for _ in
                  range(GLOBAL_POPULATION_SIZE)]
    best_solution = max(population, key=fitness_function)
    best_fitness = fitness_function(best_solution)
    checkpoint_costs = [(0, 1 / best_fitness)]  # 初始成本，迭代次数为0

    for genetic_num in range(1, GLOBAL_N_GENERATIONS + 1):
        fitness_values = [fitness_function(x) for x in population]
        next_population = selection(population, fitness_values, )
        next_population = mutation(next_population)
        population = next_population

        current_best_solution = max(population, key=fitness_function)
        current_best_cost = 1 / fitness_function(current_best_solution)
        if fitness_function(current_best_solution) > best_fitness:
            best_solution = current_best_solution
            best_fitness = fitness_function(best_solution)

        if genetic_num % checkpoint == 0:
            checkpoint_costs.append((genetic_num, current_best_cost))
            check_info = f"到达第{genetic_num}代: 最优成本 = {round(current_best_cost,3)}"
            print(check_info)
            insert_notification(check_info)

    return best_solution, best_fitness, checkpoint_costs


# 固定随机种子
def set_seed(seed):
    np.random.seed(seed)
    random.seed(seed)


def main():
    random_solution = np.random.permutation(GLOBAL_CUSTOM_LIST)
    random_routes = solution_routes(random_solution)
    random_cost = routes_cost(random_routes)
    random_img_path = plot_random_routes(random_routes, GLOBAL_COORD, random_cost, GLOBAL_DEMAND)
    static_index = random_img_path.find('\\static\\') + len('\\static\\')
    if static_index > 0:
        # 获取从'static'开始的相对路径
        random_relative_path = random_img_path[static_index:].replace('\\', '/')  # 替换所有的反斜杠为正斜杠
    else:
        random_relative_path = None  # 如果没有找到'static', 返回None


    best_solution, best_fitness, checkpoint_costs = genetic_algorithm(checkpoint=10)
    print('best_solution', best_solution)
    best_routes = solution_routes(best_solution)
    print(best_routes)
    best_cost = routes_cost(best_routes)
    print('current:', best_cost)
    file_path = plot_routes(best_routes, GLOBAL_COORD, best_cost, GLOBAL_DEMAND)
    static_index = file_path.find('\\static\\') + len('\\static\\')
    if static_index > 0:
        # 获取从'static'开始的相对路径
        relative_path = file_path[static_index:].replace('\\', '/')  # 替换所有的反斜杠为正斜杠
    else:
        relative_path = None  # 如果没有找到'static', 返回None

    # 绘制优化过程
    optimization_checkpoint_path = plot_optimization_checkpoint(checkpoint_costs)
    static_index = optimization_checkpoint_path.find('\\static\\') + len('\\static\\')
    if static_index > 0:
        # 获取从'static'开始的相对路径
        checkpoint_relative_path = optimization_checkpoint_path[static_index:].replace('\\', '/')  # 替换所有的反斜杠为正斜杠
    else:
        checkpoint_relative_path = None  # 如果没有找到'static', 返回None
    return best_cost, relative_path,random_relative_path, checkpoint_relative_path


@oe.route("/optimize", methods=["GET", 'POST'])
def optimize():
    if request.method == "GET":
        has_seen_help = session.get('has_seen_help', False)
        return render_template("optimize.html", has_seen_help=has_seen_help)

    if request.method == 'POST':
        has_seen_help = session.get('has_seen_help', False)
        # 更新 session 中的参数
        if 'file' not in request.files:
            return redirect(request.url)
        file = request.files.get('file')
        if file and allowed_file(file.filename):
            # 确保文件名安全
            reset_notifications_table()
            filename = str(timestamp) + '_' + file.filename
            # 保存文件
            file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], filename)
            file.save(file_path)
            userinfo_id = session['user_info']['id']
            with open(file_path, 'r', encoding="utf-8") as f:
                data = f.read()
            # 将上传文件的数据转为字典格式
            processed_json_string = preprocess_json_string(data)
            instance_data = json.loads(processed_json_string)
            # 获取根目录路径并拼接路径
            root_dir = current_app.root_path
            static_dir = os.path.join(root_dir, 'static', 'instance_file')
            os.makedirs(static_dir, exist_ok=True)
            # 拼接输出路径，保存本地并去读内容
            output_path = os.path.join(static_dir, f'{timestamp}_instance.vrp')
            vrp.write_instance(output_path, instance_data)
            instance = vrp.read_instance(output_path)

            global GLOBAL_DEMAND, GLOBAL_WEIGHTS, GLOBAL_CUSTOM_LIST, GLOBAL_COORD, GLOBAL_POPULATION_SIZE, GLOBAL_N_GENERATIONS, GLOBAL_MAX_CAPACITY, GLOBAL_MUTATION_RATE
            GLOBAL_DEMAND = instance['demand']
            GLOBAL_WEIGHTS = instance['edge_weight']
            GLOBAL_CUSTOM_LIST = np.arange(1, len(GLOBAL_DEMAND))
            GLOBAL_COORD = instance['node_coord'].T
            GLOBAL_POPULATION_SIZE = session.get("population_size", 1000)
            GLOBAL_N_GENERATIONS = session.get("n_generations", 100)
            GLOBAL_MAX_CAPACITY = session.get("max_capacity", 100)
            GLOBAL_MUTATION_RATE = session.get("mutation_rate", 0.5)

            # session["demand"] = g.demand.tolist()
            # session["weights"] = g.weights.tolist()
            # session["custom_list"] = g.custom_list.tolist()
            # session['coord'] = g.coord.tolist()
            set_seed(0)
            best_cost, relative_path,randomimg_path,optimization_checkpoint_path = main()
            # TRUNCATE TABLE notifications;

            db.insert_db_user_history_records(userinfo_id, file_path, best_cost, relative_path,
                                              session.get('population_size'),
                                              session.get('n_generations'), session.get('max_capacity'),
                                              session.get('mutation_rate'))
            print(relative_path,randomimg_path,optimization_checkpoint_path)

            return render_template("optimize.html", result=best_cost, relative_path=relative_path,randomimg_path=randomimg_path,optimization_checkpoint_path=optimization_checkpoint_path,has_seen_help=has_seen_help)


"""
# # 计算两点之间的距离
# def calculate_distance(point1, point2):
#     return np.sqrt((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2)
#
#
# # 适应度函数：路径总距离的倒数
# def fitness(path, coordinates):
#     assert isinstance(path, list), f"Path should be a list, got {type(path)} instead."
#     distance = sum(
#         calculate_distance(coordinates[path[i]], coordinates[path[(i + 1) % len(path)]]) for i in range(len(path)))
#     return 1 / distance
#
#
# # 初始化种群
# def create_initial_population(size, points):
#     return [random.sample(points, len(points)) for _ in range(size)]
#
#
# # 选择函数：轮盘赌选择
# def select(population, fitnesses, num_parents):
#     selected_indices = np.random.choice(range(len(population)), size=num_parents, replace=False,
#                                         p=fitnesses / np.sum(fitnesses))
#     return [population[i] for i in selected_indices]
#
#
# # 修正后的交叉（杂交）函数
# def crossover(parent1, parent2):
#     size = len(parent1)
#     idx1, idx2 = sorted(random.sample(range(size), 2))
#     offspring1 = [None] * size
#     offspring2 = [None] * size
#
#     # 填充第一个后代的部分
#     offspring1[idx1:idx2] = parent1[idx1:idx2]
#     pointer = idx2
#     for gene in parent2[idx2:] + parent2[:idx2]:
#         if gene not in offspring1:
#             if pointer >= size:
#                 pointer = 0
#             offspring1[pointer] = gene
#             pointer += 1
#
#     # 填充第二个后代的部分
#     offspring2[idx1:idx2] = parent2[idx1:idx2]
#     pointer = idx2
#     for gene in parent1[idx2:] + parent1[:idx2]:
#         if gene not in offspring2:
#             if pointer >= size:
#                 pointer = 0
#             offspring2[pointer] = gene
#             pointer += 1
#
#     return offspring1, offspring2
#
#
# # 变异函数
# def mutate(path, mutation_rate):
#     if random.random() < mutation_rate:
#         idx1, idx2 = random.sample(range(len(path)), 2)
#         path[idx1], path[idx2] = path[idx2], path[idx1]
#     return path

# 迭代开始
# def genetic_algorithm():
#     population = [np.random.permutation(session.get("custom_list")).copy() for _ in
#                   range(session.get('population_size'))]
#     best_solution = max(population, key=fitness_function)
#
#     for genetic_num in range(session.get('n_generations')):
#         fitness_values = [fitness_function(x) for x in population]
#         next_population = selection(population, fitness_values)
#         next_population = mutation(next_population)
#         population = next_population
#         current_best_solution = max(population, key=fitness_function)
#         if fitness_function(current_best_solution) > fitness_function(best_solution):
#             best_solution = current_best_solution
#         print(genetic_num, 1 / fitness_function(best_solution))
#
#     return best_solution, fitness_function(best_solution)
"""
